package ru.susu.algebra.field.quadratic;

import java.math.BigInteger;

import org.apache.commons.lang.ObjectUtils;

import ru.susu.algebra.pair.Pair;
import ru.susu.algebra.util.NumberUtilities;

/**
 * Число и з квадратичного поля. Хранит в себе два целых коэффициента: свободную
 * часть, и коэффициент перед корнем. Также хранит целое число под корнем.
 *
 * @author akargapolov
 * @since: 09.03.2009
 */
public class QuadraticInteger
{
	protected BigInteger _a;
	protected BigInteger _b;
	protected BigInteger _d;

	public QuadraticInteger()
	{
	}

	public QuadraticInteger(QuadraticInteger other)
	{
		this(other._a, other._b, other._d);
	}

	public QuadraticInteger(long a, long b, long d)
	{
		this(BigInteger.valueOf(a), BigInteger.valueOf(b), BigInteger.valueOf(d));
	}

	public QuadraticInteger(BigInteger a, BigInteger b, BigInteger d)
	{
		_a = a;
		_b = b;
		_d = d;
		freeD();
	}

	public QuadraticInteger add(QuadraticInteger addend) throws Exception
	{
		checkEqualsD(addend);
		QuadraticInteger result = new QuadraticInteger(this);
		result._a = _a.add(addend._a);
		result._b = _b.add(addend._b);
		return result;
	}

	public QuadraticInteger substruct(QuadraticInteger subs) throws Exception
	{
		checkEqualsD(subs);
		QuadraticInteger result = new QuadraticInteger(this);
		result._a = _a.subtract(subs._a);
		result._b = _b.subtract(subs._b);
		return result;
	}

	public QuadraticInteger multiply(QuadraticInteger multend) throws Exception
	{
		checkEqualsD(multend);
		QuadraticInteger result = new QuadraticInteger();
		result._a = _a.multiply(multend._a).add(_d.multiply(_b.multiply(multend._b)));
		result._b = _a.multiply(multend._b).add(_b.multiply(multend._a));
		result._d = !_d.equals(BigInteger.ZERO) ? _d : multend._d;
		return result;
	}

	public QuadraticInteger divide(QuadraticInteger dividend) throws Exception
	{
		checkEqualsD(dividend);

		// TODO написать операцию, если возможно в общем случае
		throw new Exception("Operation not realized");
	}

	/**
	 * Освобождение d от квадратов.
	 */
	private void freeD()
	{
		int sign = _d.signum();
		_d = _d.abs();
		for (Pair<Integer, Integer> pair : NumberUtilities.factorization(_d))
		{
			BigInteger key = BigInteger.valueOf(pair.getKey());
			int value = pair.getValue();
			while (value > 1)
			{
				_d = _d.divide(key).divide(key);
				_b = _b.multiply(key);
				value -= 2;
			}
		}

		_d = _d.multiply(BigInteger.valueOf(sign));
		if (_d.equals(BigInteger.ONE))
		{
			_a = _a.add(_b);
			_b = BigInteger.ZERO;
			_d = BigInteger.ZERO;
		}
	}

	protected void checkEqualsD(QuadraticInteger other) throws Exception
	{
		if (!_d.equals(other._d) && !_d.equals(BigInteger.ZERO) && !other._d.equals(BigInteger.ZERO))
			throw new Exception("To perform operation need equals d in quadratic integer");
	}

	/**
	 * Быстрое возведение в степень элемента квадратичного поля.
	 *
	 * @param pow степень
	 * @return this^pow
	 */
	public QuadraticInteger pow(BigInteger pow) throws Exception
	{
		QuadraticInteger result = this.getClass().newInstance();
		result._a = BigInteger.ONE;
		result._b = BigInteger.ZERO;
		result._d = _d;

		QuadraticInteger tmp = this;
		BigInteger two = BigInteger.valueOf(2);
		while (!pow.equals(BigInteger.ZERO))
		{
			if (pow.mod(two).intValue() == 1)
			{
				result = result.multiply(tmp);
			}
			pow = pow.divide(two);
			tmp = tmp.multiply(tmp);
		}

		return result;
	}

	/**
	 * Быстрое возведение в степень элемента квадратичного поля по модулю mod
	 *
	 * @param pow степень
	 * @param mod модуль
	 * @return this^pow
	 */
	public QuadraticInteger modPow(BigInteger pow, BigInteger mod) throws Exception
	{
		QuadraticInteger result = this.getClass().newInstance();
		result._a = BigInteger.ONE;
		result._b = BigInteger.ZERO;
		result._d = _d;

		QuadraticInteger tmp = this;
		BigInteger two = BigInteger.valueOf(2);
		while (!pow.equals(BigInteger.ZERO))
		{
			if (pow.mod(two).intValue() == 1)
			{
				result = result.multiply(tmp);
				result._a = result._a.mod(mod);
				result._b = result._b.mod(mod);
			}
			pow = pow.divide(two);
			tmp = tmp.multiply(tmp);
			tmp._a = tmp._a.mod(mod);
			tmp._b = tmp._b.mod(mod);
		}

		return result;
	}

	/**
	 * Возвращает норму элемента norm(k) = (a+b*sqrt(d))*(a-b*(sqrt(d))).
	 *
	 * @return норма элемента
	 */
	public BigInteger norm() throws Exception
	{
		QuadraticInteger other = new QuadraticInteger(this);
		other._b = other._b.multiply(BigInteger.valueOf(-1));
		other = other.multiply(this);

		if (!other._b.equals(BigInteger.ZERO))
		{
			throw new Exception("Ошибка. Норма не принадлежит Z.");
		}
		return other._a;
	}

	/**
	 * @return сопряженный элемент квадратичного поля
	 */
	public QuadraticInteger getConjugateNumber()
	{
		QuadraticInteger result = new QuadraticInteger(this);
		result._b = result._b.multiply(BigInteger.valueOf(-1));
		return result;
	}

	@Override
	public String toString()
	{
		StringBuilder res = new StringBuilder();
		res.append(_a);
		if (!_d.equals(BigInteger.ZERO) && !_b.equals(BigInteger.ZERO))
		{
			if (_b.compareTo(BigInteger.ZERO) >= 0)
				res.append(" + ");
			else
				res.append(" - ");

			if (!_b.abs().equals(BigInteger.ONE))
				res.append(_b.abs()).append("*");
			res.append("sqrt(").append(_d).append(")");
		}
		return res.toString();
	}

	public BigInteger getA()
	{
		return _a;
	}

	public BigInteger getB()
	{
		return _b;
	}

	public BigInteger getD()
	{
		return _d;
	}

	/**
	 * @return значение подкоренного выражения без извлечения квадратов
	 */
	public BigInteger getBDInSqrt()
	{
		return _b.multiply(_b).multiply(_d).abs();
	}

	@Override
	public int hashCode()
	{
		final int prime = 31;
		int result = 1;
		result = prime * result + ((_a == null) ? 0 : _a.hashCode());
		result = prime * result + ((_b == null) ? 0 : _b.hashCode());
		result = prime * result + ((_d == null) ? 0 : _d.hashCode());
		return result;
	}

	@Override
	public boolean equals(Object obj)
	{
		if (this == obj)
			return true;
		if (obj == null)
			return false;
		if (this.getClass() != obj.getClass())
			return false;
		QuadraticInteger other = (QuadraticInteger) obj;
		boolean result = ObjectUtils.equals(_a, other._a);
		result = result && ObjectUtils.equals(_b, other._b);
		result = result && ObjectUtils.equals(_d, other._d);
		return result;
	}
}
